
/**
 * Copyright 2009 cestum.org
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
 
package org.waveprotocol.wave.model.document.operation.algorithm {
import org.cestum.as3.collections.TreeMap;
import org.goverla.collections.ArrayList;
import org.goverla.collections.HashMap;
import org.waveprotocol.wave.model.document.operation.AnnotationBoundaryMap;
import org.waveprotocol.wave.model.document.operation.Attributes;
import org.waveprotocol.wave.model.document.operation.AttributesUpdate;
import org.waveprotocol.wave.model.document.operation.BufferedDocOp;
import org.waveprotocol.wave.model.document.operation.EvaluatingDocOpCursor;

/**
 * A normalizer for annotations.
 *
 */
public final class AnnotationsNormalizer implements EvaluatingDocOpCursor {
	private var target:EvaluatingDocOpCursor;
	
	private var annotationTracker:TreeMap= new TreeMap();
	private var annotationChanges:TreeMap= new TreeMap();
	
	//  private final Set ignores = new HashSet();
	
	public function AnnotationsNormalizer(target:EvaluatingDocOpCursor) {
		this.target = target;
	}
	
	
	public function finish():BufferedDocOp {
		flushAnnotations();
		return target.finish();
	}
	
	
	public function retain(itemCount:int):void {
		flushAnnotations();
		target.retain(itemCount);
	}
	
	
	public function characters(chars:String):void {
		flushAnnotations();
		target.characters(chars);
	}
	
	
	public function elementStart(type:String, attrs:Attributes):void {
		flushAnnotations();
		target.elementStart(type, attrs);
	}
	
	
	public function elementEnd():void {
		flushAnnotations();
		target.elementEnd();
	}
	
	
	public function deleteCharacters(chars:String):void {
		flushAnnotations();
		target.deleteCharacters(chars);
	}
	
	
	public function deleteElementStart(type:String, attrs:Attributes):void {
		flushAnnotations();
		target.deleteElementStart(type, attrs);
	}
	
	
	public function deleteElementEnd():void {
		flushAnnotations();
		target.deleteElementEnd();
	}
	
	
	public function replaceAttributes(oldAttrs:Attributes, newAttrs:Attributes):void {
		flushAnnotations();
		target.replaceAttributes(oldAttrs, newAttrs);
	}
	
	
	public function updateAttributes(attrUpdate:AttributesUpdate):void {
		flushAnnotations();
		target.updateAttributes(attrUpdate);
	}
	
	
	public function annotationBoundary(map:AnnotationBoundaryMap):void {
		var changeSize:int= map.changeSize();
		for (var i:int= 0; i < changeSize; ++i)  {
			startAnnotation(map.getChangeKey(i), map.getOldValue(i), map.getNewValue(i));
		}
		var endSize:int= map.endSize();
		for (var i:int= 0; i < endSize; ++i)  {
			endAnnotation(map.getEndKey(i));
		}
	}
	
	public function startAnnotation(key:String, oldValue:String, newValue:String):void {
		annotationChanges.addItem(key, new AnnotationChangeValues(oldValue, newValue));
	}
	
	public function endAnnotation(key:String):void {
		annotationChanges.addItem(key, null);
	}
	//
	//
	//  public void startAnnotation(String key, String oldValue, String newValue) {
	//    if (ValueUtils.equal(oldValue, newValue)) {
	//      if (!annotationChanges.containsKey(key)) {
	//        ignores.add(key);
	//      }
	//    } else {
	//      if (ignores.contains(key)) {
	//        ignores.remove(key);
	//      }
	//      annotationChanges.put(key, new AnnotationChangeValues(oldValue, newValue));
	//    }
	//  }
	//
	//  public void endAnnotation(String key) {
	//    if (ignores.contains(key)) {
	//      ignores.remove(key);
	//    } else {
	//      annotationChanges.put(key, null);
	//    }
	//  }
	
	private function flushAnnotations():void {
		var changes:ArrayList= new ArrayList();
		var ends:ArrayList= new ArrayList();
		//    TreeMap<String, AnnotationChangeValues> annotationChanges = this.annotationChanges;
		var size1:int = annotationChanges.keys.length;
		for(var i:int=0; i< size1; i++) {
			var key:String= annotationChanges.keys[i];
			//      String key = change.getKey();
			var values:AnnotationChangeValues= AnnotationChangeValues(annotationChanges.getItem(key));
			var previousValues:AnnotationChangeValues= AnnotationChangeValues(annotationTracker.getItem(key));
			if (values == null) {
				if (previousValues != null) {
					annotationTracker.removeItemAt(key);
					ends.addItem(key);
				}
			} else {
				if (previousValues == null || !(areEqual(values.oldValue, previousValues.oldValue)
					&& areEqual(values.newValue, previousValues.newValue))) {
					annotationTracker.addItem(key, values);
					changes.addItem(new AnnotationChange(key, values.oldValue, values.newValue));
				}
			}
		}
		if (!changes.length == 0|| !ends.length == 0) {
			target.annotationBoundary(new FlushAnnotationBoundaryMap(changes, ends));
		}
		annotationChanges.clear();
	}
	
	private static function areEqual(o1:Object, o2:Object):Boolean {
		return (o1 == null) ? o2 == null : o1 == o2;
	}
	
}
}